package cbbx_sm.decision_maker.search;

import java.util.ArrayList;
import java.util.HashSet;

public class ScheduleConfigurationNode implements SearchNode{

	// UP must always be last, otherwise funky stuff will happen.
	//public enum Schedules {C0,C1,C2,C3,C4, UP};
	//public enum Event {p0, p005,p01, p015, p02, p025, p03, p035, p04, p045, p05, p055, p06, p065, p07, p075, p08, p085, p09, p096, p1};
	//public enum Event {p0, p02, p04, p06, p08, p1};

	//public enum Event {p0, p025, p05, p075, p1};
	//public enum Event {p0,p05, p1};

	private int level;
	private String scheduleSelected;
	public double[] CiEvent;

	private final WorldStateTransition worldStateTransition;
	protected double transitionProbability;
	protected double utility;

	private final ScheduleConfigurationNode parent;
	private HashSet<SearchNode> successors;

	private final double probThreshold;
	private double pathProbability;

	private double score;
	private String bestNextSchedule;
	
	private final double zoomEventScoreFactor;
	private final double upEventScoreFactor;

	

	


	/**
	 * Creates a node with includes both the schedule and the event.
	 * Each node maintains the initial schedule decision from which it was reached, so that later it can be used to compute the expected outcome of each schedule.
	 * The node is associated with a score (all motion events captured up to the current node) and the probability of walking the path to that node.
	 * 
	 * @param scheduleSelected
	 * @param CiEvent
	 * @param C1Event
	 * @param parent
	 */
	public ScheduleConfigurationNode(String scheduleSelected,
	double[] CiEvent, ScheduleConfigurationNode parent,WorldStateTransition worldStateTransition, double probThreshold, double zoomEventScoreFactor, double upEventScoreFactor){
		this.CiEvent = CiEvent;
		this.parent = parent;
		this.transitionProbability = 1d;
		this.probThreshold = probThreshold;
		this.zoomEventScoreFactor = zoomEventScoreFactor;
		this.upEventScoreFactor = upEventScoreFactor;
		this.worldStateTransition = worldStateTransition;

		// root node.
		if (parent == null){
			utility = 0;
			transitionProbability = 1;
			this.pathProbability = 1;
			// do nothing else.
		}
		else {
			this.level = parent.level+1;			
			this.scheduleSelected = scheduleSelected;
					
			// Compute the probability of the transition from the parent state.
			this.transitionProbability = worldStateTransition.getTransitionProbability(parent.CiEvent, this.CiEvent, this.scheduleSelected);
			
			// The probability of the path from the root node.
			this.pathProbability = parent.pathProbability*this.transitionProbability;
			
			// Compute the Utility of the current state
			this.utility = getUtilityForStateAndSchedule(this.scheduleSelected, this.CiEvent, worldStateTransition, this.zoomEventScoreFactor, this.upEventScoreFactor);
		}
	}



	public static double getUtilityForStateAndSchedule(String scheduleSelected, double[] CiEvent,  WorldStateTransition worldStateTransition,
			double zoomEventScoreFactor, double upEventScoreFactor) {
		if (!scheduleSelected.equals(Schedules.UP)) {
			return zoomEventScoreFactor * CiEvent[worldStateTransition.scheduleToCameraId(scheduleSelected)];
		} else if (scheduleSelected.equals(Schedules.UP)){
			double score = 0;
			for (double e: CiEvent){
				score+=e * upEventScoreFactor;
			}
			return score;
		}
		return 0;
	}



	
	@Override
	public double getScore() {
		return score;
	}
	
	public double getTransitionProbability(){
		return transitionProbability;
	}
	
	public double getUtility(){
		return utility;
	}

	public void setScore(double score){
		this.score = score;
	}
	
	public void setBestNextAction(String schedule){
		this.bestNextSchedule = schedule;
	}
	
	@Override 
	public String getSelectedAction(){
		return scheduleSelected;
	}
	@Override
	public String getBestNextAction() {
		return bestNextSchedule;
	}
	
	@Override
	public ScheduleConfigurationNode getParent() {
		return parent;
	}


	@Override
	public HashSet<SearchNode> getSuccessors() {
		if (successors != null){
			return successors;
		}
		successors = new HashSet<SearchNode>();
		
		for (String childSchedule: this.worldStateTransition.getSchedules()){
			successors.addAll(getNextNodesForSchedule(childSchedule));
		}
		return successors;
	}
	
	/**
	 * Expands the node to the states that can be reached based on a given action.
	 * @param childSchedule the action that is taking place from the curernt node and will be the schedule of the children of the current node.
	 * @return
	 */
	public HashSet<ScheduleConfigurationNode> getNextNodesForSchedule(String childSchedule) {
		HashSet<ScheduleConfigurationNode> result = new HashSet<ScheduleConfigurationNode>();

		if (childSchedule.equals(Schedules.UP)){
			for (ScheduleConfigurationNode child: getNextStates(this, childSchedule, worldStateTransition, true, probThreshold, 
					zoomEventScoreFactor,upEventScoreFactor)){
				if (child.pathProbability>=probThreshold){
					result.add(child);
				}
			}
		} else {
			double[] CiEventNew = worldStateTransition.rollProbabilitiesForward(this.CiEvent);
			CiEventNew[worldStateTransition.scheduleToCameraId(childSchedule)] = Event.p0;
			ScheduleConfigurationNode child = new ScheduleConfigurationNode(childSchedule,CiEventNew, this, worldStateTransition, probThreshold, zoomEventScoreFactor, upEventScoreFactor);
			if (child.pathProbability>=probThreshold){
				result.add(child);
			}
			
			CiEventNew = worldStateTransition.rollProbabilitiesForward(this.CiEvent);
			CiEventNew[worldStateTransition.scheduleToCameraId(childSchedule)] = Event.p1;
			child = new ScheduleConfigurationNode(childSchedule,CiEventNew, this, worldStateTransition, probThreshold, zoomEventScoreFactor, upEventScoreFactor);
			if (child.pathProbability>=probThreshold){
				result.add(child);
			}

		}
		
		return result;
	}
	
	

	/**
	 * Generates the next level of states from a given node and a given action.
	 * @param scheduleConfigurationNode the given node.
	 * @param childSchedule the given action.
	 * @param fullyObservedStatesOnly a boolean flag indicating whether to include partial probabilities in the set of states or only fully observed states.
	 * @return the Arraylist of next states, which is all possible states in all possible zoom areas of that camera.
	 */
	public static ArrayList<ScheduleConfigurationNode> getNextStates(ScheduleConfigurationNode scheduleConfigurationNode, String childSchedule, 
			WorldStateTransition worldStateTransition, boolean fullyObservedStatesOnly, double probThreshold, double zoomEventScoreFactor, double upEventScoreFactor) {
		
		ArrayList<ScheduleConfigurationNode> result = new ArrayList<ScheduleConfigurationNode>();
		Event e = EventFactory.getInstance();
		int numberOfStates = fullyObservedStatesOnly?2:e.values().length;
		int numberOfZoomAreas = worldStateTransition.getSchedules().length-1;
		
		for (int i=0; i<Math.pow(numberOfStates, numberOfZoomAreas); i++){		
			double[] CiEventNew = new double[numberOfZoomAreas];
			int configuration = i;
			for (int j=0; j<numberOfZoomAreas; j++){
				int currentStateId = configuration % numberOfStates;
				CiEventNew[j] = fullyObservedStatesOnly?(currentStateId==0?Event.p0:Event.p1):e.values()[currentStateId];
				configuration /= (numberOfStates);
			}
			ScheduleConfigurationNode child = new ScheduleConfigurationNode(childSchedule,CiEventNew, scheduleConfigurationNode, worldStateTransition,
					probThreshold, zoomEventScoreFactor, upEventScoreFactor);
			result.add(child);
		}
		return result;
	}



	@Override
	public String toString() {
		String result = "";
		
		String motion = "";
		for (int i=0; i<worldStateTransition.getSchedules().length-1; i++){
			motion+=CiEvent[i]+":";
		}
		result = (parent==null?result += "root-> " : this.scheduleSelected) +
		":"+motion+" Utility: "+
		(utility)+" p: "+String.format("%.3f ",transitionProbability)+ "initial: "+" score: "+String.format("%.3f ",this.getScore())+" Best Schedule: "+bestNextSchedule+" level: "+level;
	
		return result;
	}	
}
